#!/bin/bash

# 错误输出及退出脚本函数
err_exit() {
    echo $1
    exit $2
}

kyo_Log() {
    echo -e "\n=============== $1($(date +'%F %T')) =================\n" >> $logfile
}

ubuntu_bridge_config() {
    local ethName="$1"
    local netconfile=/etc/network/interfaces

    cp $netconfile ${netconfile}.bak
    cat > $netconfile << EOF
# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback

auto $brname
iface $brname inet dhcp
    bridge_ports $ethName
    bridge_stp on
    bridge_fd 0
EOF
}

ubuntu_yaml_bridge_config() {
    local ethName="$1"
    local netconfile=/etc/netplan/00-installer-config.yaml
    cp $netconfile ${netconfile}.bak
    cat > $netconfile << EOF
network:
  ethernets:
    ${ethName}:
      dhcp4: no
  version: 2
  bridges:
    br0:
      interfaces: [${ethName}]
      dhcp4: yes
      addresses: [192.168.88.99/24]
      gateway4: 192.168.88.1
      nameservers:
        addresses: [192.168.88.1, 223.5.5.5]
EOF
}

# 创建网桥设备
# 第一个参数为网桥设备名, 默认值: br0
create_bridge() {
    local brname="${1-br0}"
    local ethName=$(ifconfig | grep "^e" | awk '{print $1}' | head -n 1)

    # 兼容最新系统ifconfig网卡名后有冒号
    ethName=${ethName%%:*}

    brctl addbr $brname
    brctl addif $brname $ethName
    brctl stp $brname on
    ip link set $brname up
    ifconfig $ethName 0
    dhclient $brname

    test -d /etc/netplan && ubuntu_yaml_bridge_config $ethName || ubuntu_bridge_config $ethName
}

# 创建默认NAT网络设备
# 第一个参数为NAT设备名, 默认值: default
create_nat() {
    local natname="${1-default}"
    local xmlfile
    virsh net-destroy $natname &> /dev/null
    virsh net-undefine $natname &> /dev/null

    xmlfile=$(tempfile).xml
    cat > $xmlfile << EOF
<network>
    <name>$natname</name>
    <uuid>$(cat /proc/sys/kernel/random/uuid)</uuid>
    <forward mode='nat'>
        <nat><port start="1024" end='65535 '/></nat>
    </forward>
    <bridge name='virbr0' stp='on' delay='0' />
    <mac address='52:54:00:68:44:9e '/>
    <ip address='192.168.122.1' netmask='255.255.255.0'>
        <dhcp>
            <range start='192.168.122.100' end='192.168.122.254' />
        </dhcp>
    </ip>
</network>
EOF
    virsh net-define $xmlfile
    rm $xmlfile -f
    virsh net-autostart $natname
    virsh net-start $natname
}


# 检测虚拟机名称是否使用
check_name() {
    local name="$1"
    local host="${2-1}"

    virsh dominfo "$name" &> /dev/null && return 1
    if test "$host" -eq 1 ; then
        egrep -q "^Host $name$" $sshconfig && return 1
    fi

    return 0
}

# 自动生成虚拟机的ssh config配置
gen_ssh_config() {
    local name="$1"
    local ip="$2"

    egrep -q "^Host $name$" $sshconfig && return 0

    local sshconfigdir=$(dirname $sshconfig)
    test -e $sshconfigdir || mkdir $sshconfigdir
    cat >> $sshconfig << EOF
Host $name
    HostName $ip
    User root
    PubKeyAuthentication yes
    StrictHostKeyChecking no
    IdentityFile $keyfile

EOF
}

# 后备镜像的制作虚拟机创建
# 第一个参数: 指定基础镜像名称, 默认值: local-base
# 第二个参数: 指定系统安装源服务器地址, 默认值: 127.0.0.1
virt_local_base() {
    test -e $kvmdir || mkdir $kvmdir

    local name=${1-local-base}
    local srvip=${2-127.0.0.1}
    local diskfile=$kvmdir/$name.qcow2

    check_name $name || err_exit "基础虚拟机已经存在!" 0

    qemu-img create -f qcow2 $diskfile 20G

    virt-install --name $name --ram 2048                \
        --os-type=linux                                 \
        --disk path=$diskfile,bus=virtio,format=qcow2   \
        --network bridge=$brname,model=virtio           \
        --boot hd                                       \
        --noautoconsole                                 \
        --graphics vnc,listen=0.0.0.0                   \
        -l http://$srvip/centos                         \
        -x "ks=http://$srvip/centos7.cfg" &>> $logfile

    check_name "$name" 0 && err_exit "虚拟机创建失败!" 1

    echo -e "创建\033[34;1m $name \033[0m虚拟机成功(root的密码是空格), 请耐心等待安装完成..."
    echo -e "请使用\033[34;1m virt-manager \033[0m 进入虚拟机进行自由配置"
    echo -e "\t也可以执行kvm-tools项目中的 \033[34;1m virt-base-config \033[0m 脚本进行配置"
    echo -e "配置成功后请运行以下命令生成后备镜像文件: "
    echo -e "\t\033[34;1m qemu-img convert -O qcow2 -c $diskfile $basefile \033[0m"
}

# 虚拟机环境初始化流程:
    # 安装kvm相关依赖包
    # 配置网桥网格
    # 开启网桥转发
    # 拉取基础镜像
    # 拉取基础镜像ssh私钥
virt_init() {
    which ifconfig &> /dev/null || apt install -y net-tools
    which kvm &> /dev/null || apt install -y qemu-kvm libvirt-bin
    which qemu-img &> /dev/null || apt install -y qemu-utils
    which virsh &> /dev/null || apt install -y virtinst
    which brctl &> /dev/null || apt install -y bridge-utils
    which virt-manager &> /dev/null || apt install -y virt-manager

    ifconfig | egrep -w -q "^${brname}" || create_bridge "$brname"

    # 开启防火墙允许bridge设备转发
    iptables -I FORWARD -m physdev --physdev-is-bridged -j ACCEPT

    # 检测本地后备镜像及ssh私钥是否存在， 不存在则下载
    test -e $kvmdir || mkdir $kvmdir

    local down="wget -q -O "
    which wget &> /dev/null || down="curl -s -o "

    test -e $basefile || $down $basefile http://$srv_ip/share/base.qcow2

    test -e $keyfile || $down $keyfile http://$srv_ip/master-node.key
}

# 等待虚拟机22端口是否开启
virt_wait() {
    test -z "$1" && return 1
    while ! nc -z -w 1 $1 22 &> /dev/null ; do
        sleep 1
    done
}

# 创建虚拟机流程:
    # 参数: 名称 固定IP后缀数字(192.168.88.?)
    # 检测基础镜像是否存在, 不存在则拉取基础镜像
    # 创建派生镜像
    # 创建虚拟机并启动
    # 远程连接修改虚拟机基本配置
    # 在宿主机生成ssh config配置
    # sshconfig配置名 虚拟机名称 主机名和命令提示行保持一致
virt_create() {
    local name=$1
    local ip=$2

    test -z "$name" -o -z "$ip" && err_exit "请指定要创建的虚拟机名称及IP..." 1

    grep -q '\.' <<< "$ip" || ip=${ip_net}.${ip}

    check_name $name || err_exit "虚拟机名称已经存在..."

    ping $ip -c 1 &> /dev/null && err_exit "设置 $ip 网络上已经存在..."

    local diskfile=$kvmdir/$name.qcow2

    kyo_Log "$name Create"

    echo -e "正在初始化\033[33;1m $name \033[0m虚拟机环境..."
    virt_init

    echo -e "正在创建\033[33;1m $name \033[0m虚拟机磁盘..."
    qemu-img create -f qcow2 $diskfile -F qcow2 -b $basefile 20G &>> $logfile \
                || err_exit "虚拟机磁盘失败..." 1

    echo -e "正在创建\033[33;1m $name \033[0m虚拟机..."
    virt-install --name $name --ram 2048                \
        --os-type=linux                                 \
        --disk path=$diskfile,bus=virtio,format=qcow2   \
        --network bridge=$brname,model=virtio           \
        --noautoconsole                                 \
        --boot hd                                       \
        --graphics vnc,listen=0.0.0.0 &>> $logfile

    check_name "$name" 0 && err_exit "虚拟机创建失败!" 1

    echo -e "正在启动\033[33;1m $name \033[0m虚拟机..."
    virt_wait $confip

    echo -e "正在配置\033[33;1m $name \033[0m虚拟机..."
    sudo chmod 600 $keyfile
    ssh -i $keyfile root@$confip -o StrictHostKeyChecking=no        \
        /config/centos.sh -m $name -h $name -i $ip -r &>> $logfile

    echo -e "正在重启\033[33;1m $name \033[0m虚拟机..."
    virt_wait $ip

    gen_ssh_config $name $ip

    echo -e "正在创建\033[33;1m $name \033[0m虚拟机快照..."
    virsh snapshot-create-as $name --name init &>> $logfile

    echo -e "创建\033[33;1m $name \033[0m虚拟机完成, 请使用\033[34;1m ssh $name \033[0m登录进行管理!\n"
}

# 删除虚拟机流程:
    # 参数: 名称
    # 通过virsh删除虚拟机(destroy undefine)
    # 删除虚拟机派生镜像文件
    # 删除ssh config配置
virt_del() {
    local name=$1

    test -z "$name" && err_exit "请指定要删除的虚拟机名字..." 1

    kyo_Log "$name Delete"
    echo -e "正在删除\033[33;1m $name \033[0m 虚拟机..."
    virsh destroy $name &>> $logfile
    virsh snapshot-delete $name init &>> $logfile
    virsh undefine $name &>> $logfile

    local diskfile=$kvmdir/$name.qcow2

    test -e $diskfile && rm $diskfile -f

    test -e $sshconfig || return 0

    local sline=$(egrep -n "^Host $name" $sshconfig | awk -F ':' '{print $1}')
    test -z "$sline" && return 0

    local eline=$((sline + 6))

    sed -i "${sline},${eline}d" $sshconfig
}

# 支持批量创建虚拟机
virt_add() {
    local name="$1"
    local ip="$2"
    local num="$3"

    if test -z "$num"; then
        virt_create $name $ip
        return 0
    fi

    egrep '\.' <<< $ip && err_exit "批量安装不支持此类写法!"

    for i in $(seq 1 $num) ; do
        virt_create $name-$i $[ip + i]
    done
}

# 恢复虚拟机初始配置状态
virt_reset() {
    local name="$1"

    virsh list --all | grep -w -q "$name" || err_exit "虚拟机 $name 不存在..."

    kyo_Log "$name Reset"

    virsh destroy $name

    virsh snapshot-revert $name --snapshotname init
}

kvmdir=/kvm
basefile=/kvm/base.qcow2
keyfile=/kvm/node.key
sshconfig=$HOME/.ssh/config
brname=br0
ip_net=192.168.88
srv_ip=192.168.88.88
confip=192.168.88.8
logfile=/tmp/kyo-kvm.log

cmd=$1

if test -z "$cmd"; then
    virsh list --all
    err_exit
fi

shift 1

test "$UID" -eq 0 || err_exit "请使用sudo运行..."

case "$cmd" in
    init)
        virt_init
        ;;
    add)
        virt_add "$@"
        ;;
    del)
        for i in "$@" ; do
            virt_del $i
        done
        ;;
    start)
        for i in "$@" ; do
            echo -e "正在启动\033[33;1m $i \033[0m虚拟机..."
            virsh start $i &>> $logfile
        done
        ;;
    stop)
        for i in "$@" ; do
            echo -e "正在强制关闭\033[33;1m $i \033[0m虚拟机..."
            virsh destroy $i &>> $logfile
        done
        ;;
    pause)
        for i in "$@" ; do
            echo -e "正在暂停\033[33;1m $i \033[0m虚拟机..."
            virsh suspend $i &>> $logfile
        done
        ;;
    resume)
        for i in "$@" ; do
            echo -e "正在恢复\033[33;1m $i \033[0m虚拟机暂停..."
            virsh resume $i &>> $logfile
        done
        ;;
    reboot)
        for i in "$@" ; do
            echo -e "正在重启\033[33;1m $i \033[0m虚拟机..."
            virsh reboot $i &>> $logfile
        done
        ;;
    reset)
        for i in "$@" ; do
            echo -e "正在恢复\033[33;1m $i \033[0m虚拟机出厂设置..."
            virt_reset $i
        done
        ;;
    base)
        virt_local_base "$@"
        ;;
    *)
        virsh $cmd $@
        ;;
esac

